package com.yonyou.pmclouds.basecom.conf;

import com.yonyou.pmclouds.basecom.cache.RedisCacheTransfer;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import redis.clients.jedis.JedisPoolConfig;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;


@Configuration
public class PMRedisAutoConfiguration {

    @Configuration
    @EnableConfigurationProperties(PMRedisProperties.class)
    protected static class RedisConnectionConfiguration {

        private final PMRedisProperties properties;

        private final RedisSentinelConfiguration sentinelConfiguration;

        private final RedisClusterConfiguration clusterConfiguration;

        public RedisConnectionConfiguration(PMRedisProperties properties,
                                            ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
                                            ObjectProvider<RedisClusterConfiguration> clusterConfiguration) {
            this.properties = properties;
            this.sentinelConfiguration = sentinelConfiguration.getIfAvailable();
            this.clusterConfiguration = clusterConfiguration.getIfAvailable();
        }

        @Bean
        public JedisConnectionFactory redisConnectionFactory() {
            return applyProperties(createJedisConnectionFactory());
        }

        /**
         * 将redis pool 的转给mybatis的cache，不确定合适不合适
         */
        @Bean
        public RedisCacheTransfer redisCacheTransfer(JedisConnectionFactory factory) {
            RedisCacheTransfer t = new RedisCacheTransfer();
            t.setJedisConnectionFactory(factory);
            return t;
        }

        protected final JedisConnectionFactory applyProperties(
                JedisConnectionFactory factory) {
            configureConnection(factory);
            if (this.properties.isSsl()) {
                factory.setUseSsl(true);
            }
            factory.setDatabase(this.properties.getDatabase());
            if (this.properties.getTimeout() > 0) {
                factory.setTimeout(this.properties.getTimeout());
            }
            return factory;
        }

        private void configureConnection(JedisConnectionFactory factory) {
            if (StringUtils.hasText(this.properties.getUrl())) {
                configureConnectionFromUrl(factory);
            } else {
                factory.setHostName(this.properties.getHost());
                factory.setPort(this.properties.getPort());
                if (this.properties.getPassword() != null) {
                    factory.setPassword(this.properties.getPassword());
                }
            }
        }

        private void configureConnectionFromUrl(JedisConnectionFactory factory) {
            String url = this.properties.getUrl();
            if (url.startsWith("rediss://")) {
                factory.setUseSsl(true);
            }
            try {
                URI uri = new URI(url);
                factory.setHostName(uri.getHost());
                factory.setPort(uri.getPort());
                if (uri.getUserInfo() != null) {
                    String password = uri.getUserInfo();
                    int index = password.lastIndexOf(":");
                    if (index >= 0) {
                        password = password.substring(index + 1);
                    }
                    factory.setPassword(password);
                }
            } catch (URISyntaxException ex) {
                throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url,
                        ex);
            }
        }

        protected final RedisSentinelConfiguration getSentinelConfig() {
            if (this.sentinelConfiguration != null) {
                return this.sentinelConfiguration;
            }
            PMRedisProperties.Sentinel sentinelProperties = this.properties.getSentinel();
            if (sentinelProperties != null) {
                RedisSentinelConfiguration config = new RedisSentinelConfiguration();
                config.master(sentinelProperties.getMaster());
                config.setSentinels(createSentinels(sentinelProperties));
                return config;
            }
            return null;
        }


        protected final RedisClusterConfiguration getClusterConfiguration() {
            if (this.clusterConfiguration != null) {
                return this.clusterConfiguration;
            }
            if (this.properties.getCluster() == null) {
                return null;
            }
            PMRedisProperties.Cluster clusterProperties = this.properties.getCluster();
            RedisClusterConfiguration config = new RedisClusterConfiguration(
                    clusterProperties.getNodes());

            if (clusterProperties.getMaxRedirects() != null) {
                config.setMaxRedirects(clusterProperties.getMaxRedirects());
            }
            return config;
        }

        private List<RedisNode> createSentinels(PMRedisProperties.Sentinel sentinel) {
            List<RedisNode> nodes = new ArrayList<RedisNode>();
            for (String node : StringUtils
                    .commaDelimitedListToStringArray(sentinel.getNodes())) {
                try {
                    String[] parts = StringUtils.split(node, ":");
                    Assert.state(parts.length == 2, "Must be defined as 'host:port'");
                    nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1])));
                } catch (RuntimeException ex) {
                    throw new IllegalStateException(
                            "Invalid redis sentinel " + "property '" + node + "'", ex);
                }
            }
            return nodes;
        }

        private JedisConnectionFactory createJedisConnectionFactory() {
            JedisPoolConfig poolConfig = this.properties.getPool() != null
                    ? jedisPoolConfig() : new JedisPoolConfig();

            if (getSentinelConfig() != null) {
                return new JedisConnectionFactory(getSentinelConfig(), poolConfig);
            }
            if (getClusterConfiguration() != null) {
                return new JedisConnectionFactory(getClusterConfiguration(), poolConfig);
            }
            return new JedisConnectionFactory(poolConfig);
        }

        private JedisPoolConfig jedisPoolConfig() {
            JedisPoolConfig config = new JedisPoolConfig();
            PMRedisProperties.Pool props = this.properties.getPool();
            config.setMaxTotal(props.getMaxActive());
            config.setMaxIdle(props.getMaxIdle());
            config.setMinIdle(props.getMinIdle());
            config.setMaxWaitMillis(props.getMaxWait());
            config.setMinEvictableIdleTimeMillis(props.getMinEvictableIdleTimeMillis());
            config.setNumTestsPerEvictionRun(props.getNumTestsPerEvictionRun());
            config.setTimeBetweenEvictionRunsMillis(props.getTimeBetweenEvictionRunsMillis());
            config.setTestOnBorrow(props.isTestOnBorrow());
            return config;
        }

    }


    @Configuration
    @EnableConfigurationProperties(PMRedissonProperties.class)
    protected static class RedissonConnectionConfiguration {

        private final PMRedissonProperties properties;


        public RedissonConnectionConfiguration(PMRedissonProperties properties) {
            this.properties = properties;
        }

        @Bean
        @ConditionalOnProperty(name = "spring.redisson.sentinelAddresses", matchIfMissing = false)
        public Config createRedissonConfig() {

            Config config = new Config();
            SentinelServersConfig sc = config.useSentinelServers();
            sc.setIdleConnectionTimeout(this.properties.getIdleConnectionTimeout());
            sc.setPingTimeout(this.properties.getPingTimeout());
            sc.setConnectTimeout(this.properties.getConnectTimeout());
            sc.setTimeout(this.properties.getTimeout());
            sc.setRetryAttempts(this.properties.getRetryAttempts());
            sc.setRetryInterval(this.properties.getRetryInterval());
            sc.setReconnectionTimeout(this.properties.getReconnectionTimeout());
            sc.setFailedAttempts(this.properties.getFailedAttempts());
            sc.setPassword(this.properties.getPassword());
            sc.setSubscriptionsPerConnection(this.properties.getSubscriptionsPerConnection());
            sc.setClientName(this.properties.getClientName());
            sc.setSlaveConnectionMinimumIdleSize(this.properties.getSlaveConnectionMinimumIdleSize());
            sc.setSlaveConnectionPoolSize(this.properties.getSlaveConnectionPoolSize());
            sc.setMasterConnectionMinimumIdleSize(this.properties.getMasterConnectionMinimumIdleSize());
            sc.setMasterConnectionPoolSize(this.properties.getMasterConnectionPoolSize());
            sc.setReadMode(this.properties.getReadMode());
            sc.addSentinelAddress(createSentinels());
            sc.setMasterName(this.properties.getMasterName());
            sc.setDatabase(this.properties.getDatabase());
            config.setThreads(this.properties.getThreads());
            config.setNettyThreads(this.properties.getNettyThreads());
            config.setUseLinuxNativeEpoll(this.properties.isUseLinuxNativeEpoll());

            return config;
        }

        @Bean
        @ConditionalOnProperty(name = "spring.redisson.address", matchIfMissing = false)
        public Config createSingleConfig() {
            // 用到哪些属性补充哪些属性吧
            Config config = new Config();
            SingleServerConfig singleServerConfig = config.useSingleServer();
            singleServerConfig.setAddress(this.properties.getAddress());
            singleServerConfig.setPassword(this.properties.getPassword());
            singleServerConfig.setConnectionPoolSize(this.properties.getConnectionPoolSize());
            singleServerConfig.setSubscriptionConnectionMinimumIdleSize(this.properties.getSubscriptionConnectionMinimumIdleSize());
            singleServerConfig.setSubscriptionConnectionPoolSize(this.properties.getSubscriptionConnectionPoolSize());
            singleServerConfig.setConnectionMinimumIdleSize(this.properties.getConnectionMinimumIdleSize());
            singleServerConfig.setDnsMonitoring(this.properties.isDnsMonitoring());
            singleServerConfig.setDnsMonitoringInterval(this.properties.getDnsMonitoringInterval());
            singleServerConfig.setDatabase(this.properties.getDatabase());
            config.setThreads(this.properties.getThreads());
            config.setNettyThreads(this.properties.getNettyThreads());
            config.setUseLinuxNativeEpoll(this.properties.isUseLinuxNativeEpoll());
            return config;
        }

        @Bean
        public RedissonClient redissonClient(Config config) {
            return Redisson.create(config);
        }

        private String[] createSentinels() {
            return StringUtils
                    .commaDelimitedListToStringArray(this.properties.getSentinelAddresses());
        }
    }

}
